package app.v1.schedulers;

import app.common.utils.FormsUtils;
import app.common.utils.Retry;
import app.common.utils.YdApi;
import app.common.utils.dingtalk.domain.SearchCondition;
import com.alibaba.fastjson.JSON;
import com.aliyun.dingtalkyida_1_0.models.BatchGetFormDataByIdListResponseBody;
import com.aliyun.dingtalkyida_1_0.models.GetFormDataByIDResponseBody;
import com.gitee.feizns.dynamic.Nums;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 重新加载单品成品计算表
 * 每日凌晨一点同步计算项目成本计算单
 * @author feizns
 * @since 2023/07/27
 */
@Component
public class ReloadItemCostCalculationSheet {

    /**
     * 单品成本价计算表
     */
    static final String ItemCostCalculationSheet = "FORM-5Q966D91RSTCH71663F9Q9A7QN6C3G7HBQJKLA";

    /**
     * 制品物料清单底表
     */
    static final String BottomOfProductsBomTable = "FORM-Z2666J810PK2TK7J6G5425QYSMYE2RB2UC66LI3";

    /**
     * 物料档案
     */
    static final String archivesOfMaterials = "FORM-Z2666J810PK2TK7J6G5425QYSMYE2RB2UC66LF3";

    /**
     * 重新加载项目成本计算单
     */
    @Scheduled(cron = "0 30 1 * * ?")
    public void scheduleReloadItemCostCalculationSheet() {
        //清空是否是半成品缓存
        clearIsHalfProductsTableCache();
        //清空单品成品价计算表
        clear();
        //重新计算成本价计算表接入数据
        Retry.setting(5).execute(this::reloadItemCostCalculationSheet);
    }

    public void reloadItemCostCalculationSheet() {
        AtomicInteger count = new AtomicInteger();
        //获取制品物料清单底表所有的流程实例编号
        List<String> instanceIds = YdApi.yiDaUtils.forms.instanceIds(BottomOfProductsBomTable);
        //分析产品bom为单一产品成本价格计算表
        instanceIds.parallelStream().forEach(instanceId -> Retry.setting(100).execute(() -> {
            Thread.sleep((long) (Math.random() * 5000));
            insert(instanceId);
            count.incrementAndGet();
        }));
        if ( instanceIds.size() > count.get() )
            throw new RuntimeException("数据未完全对接完毕.");
    }

    /**
     * 清空
     */
    public void clear() {
        YdApi.yiDaUtils.forms.clear(ItemCostCalculationSheet);
    }

    /**
     * 是否是完成了产品
     * @return boolean
     */
    public boolean isFinishedProduct(GetFormDataByIDResponseBody data) {
        return Objects.equals("成品", data.getFormData().get("textField_lflv2yse"));
    }

    /**
     * 插入
     * @param instanceId 实例编号
     */
    public void insert(String instanceId) {
        StopWatch watch = new StopWatch();
        watch.start();
        //获取制品数据
        GetFormDataByIDResponseBody data = YdApi.yiDaUtils.forms.getById(instanceId);
        //该数据是否已存在
        boolean exists = isExists(data);
        if ( exists )
            System.out.println("监测到数据已存在跳过传输: " + JSON.toJSONString(data));
        //是否是成品
        if ( isFinishedProduct(data) && !exists ) {
            //分析单一产品成本价格计算表
            Map<String, Object> formData = analysisProductsBomIntoASingleProductCostPriceCalculationSheet(data);
            //将转换后的数据接入单品成本价表
            try {
                YdApi.yiDaUtils.forms.insert(ItemCostCalculationSheet, formData);
                watch.stop();
                System.out.println("实例ID：" + instanceId + "耗时：" + watch.getTotalTimeSeconds() +  " 接入成功: " + formData);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 是否是存在单据
     * @param data 数据
     * @return boolean
     */
    private boolean isExists(GetFormDataByIDResponseBody data) {
        return YdApi.yiDaUtils.forms.exists(ItemCostCalculationSheet,
                SearchCondition.textEq("textField_l4xj70r0", (String) data.getFormData().get("textField_l4xj70r0")),
                SearchCondition.textEq("textField_l5rvfhbf", (String) data.getFormData().get("textField_l5rvfhbf")));
    }

    public static void main(String[] args) {
//        YdApi.yiDaUtils.forms.instanceIds("quantity_that_has_been_put_into_storage")
    }

    /**
     * 分析产品bom为单一产品成本价格计算表
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> analysisProductsBomIntoASingleProductCostPriceCalculationSheet(GetFormDataByIDResponseBody data) {
        //表单数据
        Map<String, ?> formData = data.getFormData();
        //数据
        Map<String, Object> result = new LinkedHashMap<>();
        //商家编码
        String merchantCode = (String) formData.get("textField_l4xj70r0");
        //工艺路线
        String processRoute = (String) formData.get("textField_l5rvfhbf");
        //商家编码
        result.put("textField_l4xj70r0", formData.get("textField_l4xj70r0"));
        //制品品牌
        result.put("textField_l5dbjlyf", formData.get("textField_l5dbjlyf"));
        //制品名称
        result.put("textField_l4xj70qw", formData.get("textField_l4xj70qw"));
        //规格名称
        result.put("textField_l5c1lbc9", formData.get("textField_l5c1lbc9"));
        //清单类型
        result.put("selectField_l78geswc", formData.get("selectField_l78geswc"));
        //三级分类
        result.put("textField_lflv2ysg", formData.get("textField_lflv2ysg"));
        //工艺路线
        result.put("textField_l5rvfhbf", processRoute);
        //工序列表
        //获取制品构成
        List<Map<String, Object>> subTable = (List<Map<String, Object>>) data.getFormData().get("tableField_l4xj70qy");
        //分析成品原材料及其工序
        AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo vo = analysisProductsBomIntoASingleProductCostPriceCalculationSheet(subTable);
        //原料清单
        List<Map<String, Object>> maps = vo.results;
        //子工序列表
        List<Map<String, Object>> processList = new ArrayList<>(vo.processList);
        //按商品编码分组
        Map<String, List<Map<String, Object>>> groupByMerchCode = maps.stream().collect(Collectors.groupingBy(map -> (String) map.get("selectField_l5ltx8cu")));
        //给对应组的内容进行数量及总价合并
        maps = mergeByMerchCode(groupByMerchCode);
        //存储原材料列表
        result.put("tableField_l4xj70qy", maps);
        //添加本成品工序
        processList.addAll(analysisProcess(merchantCode));
        //统计
        multiplyProcessWorkingHours(processList, BigDecimal.ONE);
        //合并工序列表
        processList = mergeByMerchCodeAndProcess(processList);
        //提取工序列表
        result.put("tableField_lkjqbvyh", processList);
        //抓取物料档案1
        GetFormDataByIDResponseBody one = YdApi.yiDaUtils.forms.getOne(archivesOfMaterials, SearchCondition.textEq("textField_e0cqzpv", merchantCode));
        //货品编号
        result.put("textField_lkkm4lq1", one != null ? one.getFormData().get("textField_ss4ttrr") : "");
        //产品等级
        result.put("textField_lkkm4lpw", one != null ? one.getFormData().get("selectField_h7tbrv4") : "");
        //加工总成本
        BigDecimal processTotalPrice = processTotalPrice(processList);
        result.put("numberField_lkjqbvyo", processTotalPrice);
        //原料总成本
        BigDecimal totalCostOfRawMaterial = totalCostOfRawMaterial(maps);
        //原料总价
        result.put("numberField_lkjqbvyp", totalCostOfRawMaterial);
        //商品总成本 = 加工总成本 + 原料总成本
        result.put("numberField_l5c78mer", processTotalPrice.add(totalCostOfRawMaterial));
        //返回数据
        return result;
    }

    /**
     * 合并由营销代码和过程
     * @param processList 进程列表
     * @return {@link List}<{@link Map}<{@link String}, {@link Object}>>
     */
    private static List<Map<String, Object>> mergeByMerchCodeAndProcess(List<Map<String, Object>> processList) {
        Map<String, List<Map<String, Object>>> map = processList.stream()
                .collect(Collectors.groupingBy(process -> process.get("textField_lkjqbvyj") + "-" + process.get("textField_lkjqbvyl")));
        //将工价合并到第一项并返回
        return map.values().stream().map(v -> {
            Map<String, Object> first = v.get(0);
            //工价
            Optional<BigDecimal> wages = v.stream()
                    .map(item -> (Number) item.get("numberField_lkjqbvym"))
                    .map(ReloadItemCostCalculationSheet::castBigDecimal)
                    .reduce(BigDecimal::add);
            //获取重置工价集合
            first.put("numberField_lkjqbvym", wages.get());
            //
            return first;
        }).collect(Collectors.toList());
    }

    /**
     * 投大小数
     * @param number 数量
     * @return {@link BigDecimal}
     */
    private static BigDecimal castBigDecimal(Object number) {
        return BigDecimal.valueOf(Nums.of(number, BigDecimal.ZERO).doubleValue());
    }

    /**
     * 过程总价格
     * @param processList 进程列表
     * @return {@link BigDecimal}
     */
    private static BigDecimal processTotalPrice(List<Map<String, Object>> processList) {
        return processList.stream()
                .map(item -> {
                    Number tmp = (Number) item.get("numberField_lkjqbvym");
                    return tmp != null ? BigDecimal.valueOf(tmp.doubleValue()) : BigDecimal.ZERO;
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

//    public static void main(String[] args) {
//        GetFormDataByIDResponseBody data = YdApi.yiDaUtils.forms.getById("FINST-8G866MC13EJAVOHGBTIG99U4PCZT2THFUNHHLR8G");
//        Map<String, Object> map = analysisProductsBomIntoASingleProductCostPriceCalculationSheet(data);
//        System.out.println(JSON.toJSONString(map));
////        System.out.println();
////        List<Map<String, Object>> maps = analysisProcess("Y-wkjj1015c", BigDecimal.ONE);
////        multiplyProcessWorkingHours(maps, BigDecimal.ONE);
////        System.out.println(maps);
//    }

    /**
     * 按商家编号合并价格
     * @param groupByMerchCode 集团通过营销代码
     */
    private static List<Map<String, Object>> mergeByMerchCode(Map<String, List<Map<String, Object>>> groupByMerchCode) {
        return groupByMerchCode.values().stream()
                .map(maps -> {
                    //第一个元素
                    Map<String, Object> first = maps.get(0);
                    //统计用量
                    BigDecimal num = maps.stream()
                            .map(o -> (BigDecimal) o.get("numberField_l4xj70r3"))
                            .reduce(BigDecimal.ZERO, BigDecimal::add);
                    first.put("numberField_l4xj70r3", num);
                    //原料总成本
                    BigDecimal totalCost = maps.stream()
                            .map(o -> (BigDecimal) o.get("numberField_lkjqbvyq"))
                            .reduce(BigDecimal.ZERO, BigDecimal::add);
                    first.put("numberField_lkjqbvyq", totalCost);
                    return first;
                }).collect(Collectors.toList());
    }

    /**
     * 总成本原材料
     * @return {@link BigDecimal}
     */
    public static BigDecimal totalCostOfRawMaterial(List<Map<String, Object>> maps) {
        BigDecimal result = BigDecimal.ZERO;
        for (Map<String, Object> map : maps)
            result = result.add((BigDecimal) map.getOrDefault("numberField_lkjqbvyq", BigDecimal.ZERO));
        return result;
    }

    /**
     * 分析产品物料清单成一个产品成本价格计算表响应对象
     * @author feizns
     * @since 2023/07/28
     */
    public static class AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo {

        /**
         * 结果
         */
        public final List<Map<String, Object>> results = new ArrayList<>();

        /**
         * 进程列表
         */
        public final List<Map<String, Object>> processList = new ArrayList<>();

    }

    /**
     * 分析产品bom为单一产品成本价格计算表
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    @SuppressWarnings("unchecked")
    private static AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo analysisProductsBomIntoASingleProductCostPriceCalculationSheet(List<Map<String, Object>> subTable) {
        //响应参数
        AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo vo = new AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo();
        //所有子产品
        for (Map<String, Object> product : subTable) {
            //
            Map<String, Object> result = new LinkedHashMap<>();
            //商家编码
            String subMerchantCode = merchantCode(product);
            //用量
            BigDecimal dosage = castBigDecimal(product.get("numberField_l4xj70r3"));
            //价格
            BigDecimal price = FormsUtils.getPrice(subMerchantCode);
            //工艺路线
            String processRoute = (String) product.get("textField_l9h1ke40");
            //是否是半成品
            if ( isHalfProductsTableFromCache(subMerchantCode) ) {
                //获取制品数据
                GetFormDataByIDResponseBody data = YdApi.yiDaUtils.forms.getOne(BottomOfProductsBomTable,
                        SearchCondition.textEq("textField_l4xj70r0", subMerchantCode),
                        SearchCondition.textEq("textField_l5rvfhbf", processRoute)
                );
                AnalysisProductsBomIntoASingleProductCostPriceCalculationSheetVo subVo = null;
                if ( data != null ) {
                    //获取表单数据
                    List<Map<String, Object>> sbTable = (List<Map<String, Object>>) data.getFormData().get("tableField_l4xj70qy");
                    //递归分析半成品原材料
                    subVo = analysisProductsBomIntoASingleProductCostPriceCalculationSheet(sbTable);
                    //将原材料数量 * 半成品数量
                    multiply(subVo.results, dosage);
                    //添加所有商品
                    vo.results.addAll(subVo.results);
                }
                //抓取工序
                List<Map<String, Object>> subProcess = analysisProcess(subMerchantCode);
                //添加子工序
                if ( subVo != null )
                    subProcess.addAll(subVo.processList);
                //将当前节点的子工序值进行统计
                multiplyProcessWorkingHours(subProcess, dosage);
                //添加到工序列表
                vo.processList.addAll(subProcess);
            } else {
                //商家编码
                result.put("selectField_l5ltx8cu", product.get("selectField_l5ltx8cu"));
                //货品名称
                result.put("textField_l4xj70qz", product.get("textField_l4xj70qz"));
                //规格名称
                result.put("textField_l4xj70r1", product.get("textField_l4xj70r1"));
                //货品名称
                result.put("textField_l5c8ejpq", product.get("textField_l4xj70qz"));
                //单位
                result.put("textField_l5ltx8cw", product.get("textField_l5ltx8cw"));
                //用量
                result.put("numberField_l4xj70r3", dosage);
                //成本
                result.put("numberField_lkjqbvyn", price);
                //原料总成本
                result.put("numberField_lkjqbvyq", price.multiply(dosage));
                //
                vo.results.add(result);
            }
        }
        return vo;
    }

    /**
     * 分析过程
     * 分析工序
     * @param merchantCode 商业代码
     * @return {@link List}<{@link Map}<{@link String}, {@link Object}>>
     */
    public static List<Map<String, Object>> analysisProcess(String merchantCode) {
        //获取该编码工序列表
        List<BatchGetFormDataByIdListResponseBody.BatchGetFormDataByIdListResponseBodyResult> processList = FormsUtils.processList(merchantCode);
        return processList.stream().map(process -> {
            Map<String, Object> result = new LinkedHashMap<>();
            //工价
            BigDecimal wages = castBigDecimal(process.getFormData().get("numberField_lfgk4kjx"));
            //商家编码
            result.put("textField_lkjqbvyj", process.getFormData().get("textField_lfgk4kjq"));
            //工序
            result.put("textField_lkjqbvyl", process.getFormData().get("textField_lfgk4kjt"));
            //用量
            result.put("numberField_lklxnv7v", BigDecimal.ONE);
            //工序工价
            result.put("numberField_lklxnv7w", wages);
            //总工价
            result.put("numberField_lkjqbvym", wages);
            return result;
        }).collect(Collectors.toList());
    }

    /**
     * 繁殖过程工作时间
     * @param processList 进程列表
     */
    private static void multiplyProcessWorkingHours(List<Map<String, Object>> processList, BigDecimal dosage) {
        for (Map<String, Object> map : processList) {
            //用量
            BigDecimal originalDosage = (BigDecimal) map.get("numberField_lklxnv7v");
            //总工价
            BigDecimal originalTotalWages = (BigDecimal) map.get("numberField_lkjqbvym");
            //重新计算用量
            map.put("numberField_lklxnv7v", originalDosage.multiply(dosage));
            //重新计算总工价
            map.put("numberField_lkjqbvym", originalTotalWages.multiply(dosage));
        }
    }

    /**
     * 乘
     * @param subTable 子表
     */
    public static void multiply(List<Map<String, Object>> subTable, BigDecimal num) {
        for (Map<String, Object> map : subTable) {
            //原数量
            BigDecimal originalNum = ((BigDecimal) map.get("numberField_l4xj70r3"));
            //重新计算数量
            map.put("numberField_l4xj70r3", originalNum.multiply(num));
            //原原料总成本
            BigDecimal originalTotalCostOfRawMaterial = castBigDecimal(map.get("numberField_lkjqbvyq"));
            //原料总成本
            map.put("numberField_lkjqbvyq", originalTotalCostOfRawMaterial.multiply(num));
        }
    }

    /**
     * 商业代码
     * @param product 产品
     * @return {@link String}
     */
    private static String merchantCode(Map<String, Object> product) {
        return (String) product.get("selectField_l5ltx8cu");
    }

    /**
     * 是否是半产品
     * @param merchantCode 商业代码
     * @return boolean
     */
    private static boolean isHalfProduct(String merchantCode) {
        GetFormDataByIDResponseBody one = YdApi.yiDaUtils.forms.getOne("FORM-Z2666J810PK2TK7J6G5425QYSMYE2RB2UC66LF3",
                SearchCondition.textEq("textField_e0cqzpv", merchantCode)
        );
        Object primaryClassification = one != null ? one.getFormData().get("selectField_rjugrt3") : "";
        return Objects.equals(primaryClassification, "半成品");
    }

    /**
     * 是否是半成品 会缓存结果
     * @param merchantCode 商家编码
     * @return boolean
     */
    private static boolean isHalfProductsTableFromCache(String merchantCode) {
        if ( !isHalfProductsTable.containsKey(merchantCode) )
            isHalfProductsTable.put(merchantCode, isHalfProduct(merchantCode));
        return isHalfProductsTable.get(merchantCode);
    }

    /**
     * 是否是半产品表
     */
    private static Map<String, Boolean> isHalfProductsTable = new ConcurrentHashMap<>();

    /**
     * 清空半成品缓存
     */
    private void clearIsHalfProductsTableCache() {
        isHalfProductsTable = new ConcurrentHashMap<>();
    }

}
